package red.stu.pconstraint.engine.reflect.method;

import org.jetbrains.annotations.NotNull;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author yihrmc @stu.red
 * @date 2019/11/16 9:34
 */
public final class MethodCallUtil {

    /**
     * 动态调整参数执行方法 <br/>
     *      - 根据参数类型进行分配(如果类型重复，则只取第一个)
     * @param method 被执行方法
     * @param obj 被执行方法的对象实例
     * @param args 动态参数
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static Object invoke(Method method, Object obj, Object[] args) throws InvocationTargetException, IllegalAccessException {
        Class<?>[] parameterTypes = method.getParameterTypes();
        return method.invoke(obj, sort(parameterTypes, args));
    }

    private static Object[] sort(Class<?>[] parameterTypes, Object[] args) {
        Object[] sortObjs = new Object[parameterTypes.length];
        parameterTypesFor:
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> parameterType = parameterTypes[i];
            for (Object arg : args) {
                Class<?> aClass = arg.getClass();
                if (parameterType.isAssignableFrom(aClass)) {
                    sortObjs[i] = arg;
                    continue parameterTypesFor;
                }
            }
            throw new MethodCallException("Method parameter type is not find: " + parameterType.getName());
        }
        return sortObjs;
    }

    public static Method getMethod(@NotNull String className, @NotNull String methodName, @NotNull Class<?>[] parameterTypes) throws ClassNotFoundException {
        Class<?> aClass = Class.forName(className);
        return getMethod(aClass, methodName, parameterTypes);
    }

    public static Method getMethod(@NotNull Class<?> aClass, @NotNull String methodName, @NotNull Class<?>[] parameterTypes) {
        Method[] methods = aClass.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class<?>[] types = method.getParameterTypes();
                if (parameterTypes.length == types.length) {
                    boolean flag = true;
                    eachTypes:
                    for (Class<?> type : types) {
                        for (Class<?> parameterType : parameterTypes) {
                            if (!parameterType.isAssignableFrom(type)) {
                                flag = false;
                                break eachTypes;
                            }
                        }
                    }
                    if (flag) {
                        return method;
                    }
                }
            }
        }
        return null;
    }

}
